#ifndef __DATABASE_SERIALIZABLE_H__
  #define __DATABASE_SERIALIZABLE_H__

#include <string>
#include <map>

#include "serializable.h"
#include "smart_pointer.h"
#include "database.h"
#include "safe_pointer.h"

using namespace std;

//! An implementation for serializable, which uses a database as its storage media

/*!
Any object which is inherited from this class can save its state into a given database or load its member
variables from the given database object.

The table with the given name must already exist in the database. Also a unique ID (or a primary key) is required for such a
table (id-column must be in lowercase: "id").

Here is the SQL statement to create a table required for the following example (all names are case sensitive):

\code
CREATE TABLE my_class
(
  id bigint NOT NULL,
  application_name character varying,
  CONSTRAINT my_class PRIMARY KEY (id)
)
\endcode

Implementation date: 2008

Usage:

\code
class my_class : public database_serializable {

private:
  string application_name;

public:

  const string &get_application_name() const {
    return this->application_name;
  }

  virtual void load() {
    DESERIALIZE(application_name);
  }

  virtual void persist(bool requires_lock = true) {
    SERIALIZE(application_name);
    database_serializable::persist(requires_lock);
  }

  my_class() : database_serializable("my_class") {
    // nothing to do
  }

  virtual ~my_class() {
    // nothing to do
  }
};
\endcode

The database_serializable class requires a database implementation to communicate with the database. This can be
set once and be reused in the whole life time of the application. For example you can call the following method
once the application starts (recalling this method would cause a reconnect, which may be useful under some
circumstances):

\code
void set_db_serialization_database() {
  database_serializable::set_database(new postgre_database()); // the ownership of the database object will be transferred to database_serializable
  database_serializable::get_database()->connect("127.0.0.1", "postgres", "passme", "postgres", "db_protocol.txt");
}
\endcode

Instances of the my_class should be used as:

\code
my_class mc;
mc.set_id(0ULL); // or any id number as an unsigned long long integer
... // you can set other properties of the mc here ...

mc.persist(); // data will be written to the database (may cause an INSERT or an UPDATE)
...

mc.load(); // data will be loaded from the database (SELECT)
\endcode

\attention if set_id() is not called before calling persist(), then a new id will be assigned automatically.
*/

class database_serializable : public serializable {

template <class T>
friend class object_record_set;

private:
  static smart_pointer<database> db;

  struct column_content {
    string content;
    bool column_must_be_loaded; // if true then only selected columns will be loaded

    column_content() : column_must_be_loaded(false) {
    }
  };

  //! holds the values, called by serialize method (will be needed to insert / update)
  map<string, column_content> values;

  //! id of the current serializable object, it must be set before loading or saving the object
  unsigned long long id;

  //! holds the loaded data from the database (will be needed to select)
  record_set *rs;

  //! will be used by class object_record_set
  unsigned int row;

  //! whether the id is set or not
  bool id_set;

  //! whether value changes should be protocolled or not
  /*!
  database requirements:

  CREATE TABLE protocol_table
  (
     id bigserial NOT NULL,
     change_timestamp timestamp with time zone NOT NULL DEFAULT now(),
     old_value character varying,
     new_value character varying,
     table_name character varying,
     column_name character varying,

     PRIMARY KEY (id)
  )

  it only protocols updates; this means that the database_serializable object must be loaded before
  */
  bool protocol_changes;

  void insert() const;
  void update() const;

  void protocol() const;

  bool all_fields_must_be_loaded() const;

protected:
  //! serializes the given variable
  virtual void serialize(const string &variable_name, const bool value);

  //! serializes the given variable
  virtual void serialize(const string &variable_name, const int value);

  //! serializes the given variable
  virtual void serialize(const string &variable_name, const long long value);

  //! serializes the given variable
  virtual void serialize(const string &variable_name, const double value);

  //! serializes the given variable
  virtual void serialize(const string &variable_name, const string &value, bool escape_apostrophe = true);

  //! deserializes the given variable
  virtual void deserialize(bool &output, const string &variable_name);

  //! deserializes the given variable
  virtual void deserialize(int &output, const string &variable_name);

  //! deserializes the given variable
  virtual void deserialize(long long &output, const string &variable_name);

  //! deserializes the given variable
  virtual void deserialize(double &output, const string &variable_name);

  //! deserializes the given variable
  virtual void deserialize(string &output, const string &variable_name);

  //! loads the data from the database
  virtual void load();

  //! writes the data back to the database. it can lock the table to make sure no conflicts happen
  virtual void persist(bool requires_lock = true);

  //! constructor
  database_serializable(const char * const class_name, bool protocol_changes = false);

  //! destructor
  virtual ~database_serializable();

public:

  //! returns true if connected to the database
  static bool is_connected();

  //! sets the database object which will be used for database communications
  /*!
  the ownership of the db will be transferred to database_serializable.
  db must be allocated using new, because it will be deleted when the program ends (static variable).
  */
  static void set_database(database *db);

  //! returns the database object
  /*!
  do not delete or manipulate the returned pointer
  */
  static safe_pointer<database> get_database();

  //! removes the row with the current id from the database (may throw an exception)
  virtual void remove();

  //! sets the id of the current object
  void set_id(unsigned long long id);

  //! returns the id of the current object
  unsigned long long get_id() const;

  //! returns true if the given field has a null-value also if no record was found (may throw an exception)
  /*!
  this method may call the load method (if not already loaded)
  */
  bool is_null(const string &column_name);

  //! clears all registered columns
  virtual void clear_all_registered_columns();

  //! registers a column to be loaded when load() is called
  virtual void register_column(const string &column_name);

  //! unregisters a column. if the column is not registered, then nothing happens
  virtual void unregister_column(const string &column_name);
};

#endif
